/**
 * 分片上传类
 */
export class ChunkUploader {
  /**
   * @param {File} file - 要上传的文件
   * @param {Object} options - 配置选项
   * @param {number} [options.chunkSize=2097152] - 分片大小，默认为2MB
   * @param {string} options.url - 上传的URL
   * @param {Function} [options.onProgress] - 上传进度的回调函数
   * @param {number} [options.concurrentUploads=5] - 同时上传的分片数量，默认为5
   * @param {Function} [options.onChunkStatusChange] - 分片状态改变的回调函数
   */
  constructor(file, options) {
    this.file = file
    this.uploadQueue = []
    this.id = this.generateUploadId()
    this.options = options
    this.chunkSize = options.chunkSize || 1024 * 1024 * 2
    this.concurrentUploads = options.concurrentUploads || 5
    this.totalChunks = Math.ceil(file.size / this.chunkSize)
    this.results = this.getStatusFormCache() || new Array(this.totalChunks)
    this.uploadedSize = this.getUploadedChunksIndex().length * this.chunkSize
    this.uploaded = Math.ceil((this.uploadedSize / this.file.size) * 100)
    this.uploading = false
    this.onProgress = options.onProgress || function () {}
    this.onChunkStatusChange = options.onChunkStatusChange || function () {}
    this.abortController = new AbortController()
  }

  /**
   * 从localStorage获取已上传的分片状态
   * @returns {Object[]} 已上传的分片的状态数组
   */
  getStatusFormCache() {
    const cache = localStorage.getItem(this.id)
    if (cache) {
      const { results, lastModified, chunkSize } = JSON.parse(cache)
      if (
        // 分片大小不一样，需要重新上传
        chunkSize !== this.chunkSize ||
        // 7天过期(这里最好跟后端一致)
        Date.now() - lastModified > 7 * 24 * 60 * 60 * 1000
      ) {
        localStorage.removeItem(this.id)
        return
      }
      return results
    }
  }

  /**
   * 将已上传的分片状态保存到localStorage
   * @param {number} chunkIndex - 已上传的分片的索引
   * @param {Object} value - 分片的状态
   */
  setUploadStatus(chunkIndex, value) {
    this.results[chunkIndex] = value
    localStorage.setItem(
      this.id,
      JSON.stringify({
        results: this.results,
        lastModified: Date.now(),
        chunkSize: this.chunkSize
      })
    )
  }

  clearUploadedChunks() {
    localStorage.removeItem(this.id)
  }

  generateUploadId() {
    return `${this.file.name}-${this.file.size}-${this.file.lastModified}`
  }

  appendExtraParams(formData) {
    const params = this.options.extraParams || {}
    for (const key in params) {
      formData.append(key, params[key])
    }
  }

  /**
   * 上传一个分片
   * @param {number} chunkIndex - 要上传的分片的索引
   * @returns {Promise<Object>} 上传的结果，如果成功，返回服务器的响应数据
   */
  async uploadChunk(chunkIndex) {
    this.results[chunkIndex] = { status: 'pending' }

    const chunk = this.file.slice(
      chunkIndex * this.chunkSize,
      (chunkIndex + 1) * this.chunkSize
    )
    const formData = new FormData()
    formData.append('chunkData', chunk)
    formData.append('fileCode', this.id)
    formData.append('fileName', this.file.name)
    formData.append('chunkNumber', chunkIndex)
    formData.append('totalChunks', this.totalChunks)
    formData.append('fileSize', this.file.size)

    // 添加额外参数
    this.appendExtraParams(formData)

    try {
      this.onChunkStatusChange(chunkIndex, 'uploading')
      const res = await fetch(this.options.url, {
        method: 'POST',
        body: formData,
        headers: {
          // 此处为跟后端约定的token
          Authorization: localStorage.getItem('token')
        },
        signal: this.abortController.signal
      })
      const data = await res.json()

      // 跟后端约定的code
      if (res.ok && data.code === 200) {
        this.uploadedSize += chunk.size
        this.uploaded = Math.ceil((this.uploadedSize / this.file.size) * 100)
        this.onProgress(this.uploaded)
        this.setUploadStatus(chunkIndex, {
          status: 'fulfilled',
          value: data
        })
        this.onChunkStatusChange(chunkIndex, 'uploaded')
        return data
      } else {
        this.results[chunkIndex] = { status: 'rejected', reason: data }
        this.onChunkStatusChange(chunkIndex, 'error')
        throw new Error(data.message || '上传失败')
      }
    } catch (error) {
      this.results[chunkIndex] = { status: 'rejected', reason: error }
      this.onChunkStatusChange(chunkIndex, 'error')
      throw error
    }
  }

  /**
   * 获取已上传的分片的索引数组
   * @returns {number[]} 已上传的分片的索引数组
   */
  getUploadedChunksIndex() {
    return this.results.reduce((acc, result, index) => {
      if (result && result.status === 'fulfilled') {
        acc.push(index)
      }
      return acc
    }, [])
  }

  /**
   * 上传文件
   * @returns {Promise<Array>} 上传的结果，数组中的每个元素代表一个分片的上传结果
   */
  async upload() {
    if (this.uploading) {
      return
    }
    this.uploading = true
    this.uploadQueue = []
    const uploadedChunksIndex = this.getUploadedChunksIndex()
    for (let i = 0; i < this.totalChunks; i++) {
      if (!uploadedChunksIndex.includes(i)) {
        this.uploadQueue.push(i)
      }
    }

    while (this.uploadQueue.length > 0) {
      const uploadPromises = []

      for (
        let i = 0;
        i < this.concurrentUploads && this.uploadQueue.length > 0;
        i++
      ) {
        const chunkIndex = this.uploadQueue.shift()
        uploadPromises.push(this.uploadChunk(chunkIndex))
      }
      await Promise.allSettled(uploadPromises)
    }

    this.uploading = false
    // 如果所有分片都上传完成，清除localStorage中的缓存
    if (this.uploaded === 100) {
      this.onProgress(this.uploaded)
      this.clearUploadedChunks()
    } else {
      throw new Error('上传失败')
    }
    return this.results
  }

  /**
   * 取消上传
   */
  cancel() {
    this.abortController.abort()
    this.uploading = false
    this.uploadQueue = []
  }

  get progress() {
    return this.uploaded
  }
}
